package cn.anecansaitin.whimsy.common.entity;

import cn.anecansaitin.whimsy.common.register.ModEntities;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.joml.Quaternionf;
import org.joml.Vector3f;

import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;

//todo 这个实现实在是太丑陋了，为什么不直接使用射线追踪？
public class WeaponSwingCollision extends Projectile {
    private static final EntityDataAccessor<Float> DATA_DAMAGE = SynchedEntityData.defineId(WeaponSwingCollision.class, EntityDataSerializers.FLOAT);
    private Set<Entity> ignoredEntities;
    private final Set<MobEffectInstance> effects;
    private final Consumer<Entity> onHit;

    public WeaponSwingCollision(EntityType<? extends Projectile> pEntityType, Level pLevel) {
        super(pEntityType, pLevel);
        ignoredEntities = new HashSet<>();
        effects = new HashSet<>();
        onHit = (p) -> {};
    }

    private WeaponSwingCollision(Entity owner, float damage, Set<Entity> ignoredEntities, Set<MobEffectInstance> effects, Consumer<Entity> onHit) {
        this(owner, owner.position().add(0, owner.getEyeHeight(), 0), damage, ignoredEntities, effects, onHit);
    }

    private WeaponSwingCollision(Entity owner, Vec3 pos, float damage, Set<Entity> ignoredEntities, Set<MobEffectInstance> effects, Consumer<Entity> onHit) {
        super(ModEntities.WEAPON_SWING_COLLISION.get(), owner.level());
        this.ignoredEntities = ignoredEntities;
        this.effects = effects;
        setDamage(damage);
        setPos(pos.x, pos.y, pos.z);
        setOwner(owner);
        this.ignoredEntities = ignoredEntities;
        this.onHit = onHit;
    }

    @Override
    public void tick() {
        super.tick();

        while (!isRemoved()) {
            EntityHitResult result = findHitEntity(position(), position().add(getDeltaMovement()), ignoredEntities);

            if (result == null) {
                discard();
                break;
            }

            Entity entity = result.getEntity();
            Entity owner = getOwner();
            //判断是否一个队伍中的，是否开启了友伤。
            if (entity instanceof Player && owner instanceof Player && !((Player) owner).canHarmPlayer((Player) entity)) {
                ignoredEntities.add(entity);
                continue;
            }

            onHitEntity(result);
            ignoredEntities.add(entity);
        }
    }

    @Override
    protected void onHitEntity(EntityHitResult result) {
        Entity owner = getOwner();
        Entity entity = result.getEntity();
        DamageSource source;

        if (getDamage() != 0) {
            if (owner == null) {
                source = damageSources().generic();
            } else if (owner instanceof Player player) {
                source = damageSources().playerAttack(player);
            } else if (owner instanceof Mob mob) {
                source = damageSources().mobAttack(mob);
            } else {
                source = damageSources().generic();
            }

            entity.hurt(source, getDamage());
        }

        effects.forEach(effect -> {
            if (entity instanceof LivingEntity livingEntity) {
                livingEntity.addEffect(effect, owner);
            }
        });

        onHit.accept(entity);
    }

    @Nullable
    private EntityHitResult findHitEntity(Vec3 startVec, Vec3 endVec, Set<Entity> ignoredEntities) {
        return ProjectileUtil.getEntityHitResult(this.level(), this, startVec, endVec, this.getBoundingBox().expandTowards(this.getDeltaMovement()).inflate(1.0D), (e) -> !ignoredEntities.contains(e) && canHitEntity(e));
    }

    private float getDamage() {
        return getEntityData().get(DATA_DAMAGE);
    }

    public void setDamage(float damage) {
        getEntityData().set(DATA_DAMAGE, damage);
    }

    @Override
    protected void defineSynchedData() {
        getEntityData().define(DATA_DAMAGE, 0f);
    }

    @Override
    protected void addAdditionalSaveData(CompoundTag compound) {
        super.addAdditionalSaveData(compound);
        compound.putFloat("damage", getDamage());
    }

    @Override
    protected void readAdditionalSaveData(CompoundTag compound) {
        super.readAdditionalSaveData(compound);
        setDamage(compound.getFloat("damage"));
    }

    @Override
    public void shootFromRotation(Entity pShooter, float pX, float pY, float pZ, float pVelocity, float pInaccuracy) {
        float f = -Mth.sin(pY * ((float) Math.PI / 180F)) * Mth.cos(pX * ((float) Math.PI / 180F));
        float f1 = -Mth.sin((pX + pZ) * ((float) Math.PI / 180F));
        float f2 = Mth.cos(pY * ((float) Math.PI / 180F)) * Mth.cos(pX * ((float) Math.PI / 180F));
        this.shoot(f, f1, f2, pVelocity, pInaccuracy);
        Vec3 vec3 = pShooter.getDeltaMovement();
        this.setDeltaMovement(this.getDeltaMovement().add(vec3.x, 0.0D, vec3.z));
    }

    private static void shootAtAngle(Projectile collision, Entity attacker, float angle, float radius) {
        Vec3 vec3 = attacker.getUpVector(1.0F);
        Quaternionf quaternionf = (new Quaternionf()).setAngleAxis(angle * ((float) Math.PI / 180F), vec3.x, vec3.y, vec3.z);
        Vec3 vec31 = attacker.getViewVector(1.0F);
        Vector3f vector3f = vec31.toVector3f().rotate(quaternionf);
        collision.shoot(vector3f.x(), vector3f.y(), vector3f.z(), radius, 0);
    }

    public Vec3 getFinalPos() {
        return position().add(getDeltaMovement());
    }

    //以下为生成判定线方法
    public static WeaponSwingCollision[] sectorRange(Entity attacker, float radius, float angle, int count, float damage) {
        return sectorRange(attacker, radius, angle, count, damage, new HashSet<>(), (p) -> {});
    }

    public static WeaponSwingCollision[] sectorRange(Entity attacker, float radius, float angle, int count, Set<MobEffectInstance> effects) {
        return sectorRange(attacker, radius, angle, count, 0, effects, (p) -> {});
    }

    public static WeaponSwingCollision[] sectorRange(Entity attacker, float radius, float angle, int count, float damage, Set<MobEffectInstance> effects, Consumer<Entity> onHit) {
        HashSet<Entity> ignoredEntities = new HashSet<>();

        if (count % 2 == 0) {
            count++;
        }

        WeaponSwingCollision[] collisions = new WeaponSwingCollision[count];
        int j = count / 2 + 1;
        float interval = angle / count - 1;

        for (int i = 1; i < j; i++) {
            WeaponSwingCollision collision1 = new WeaponSwingCollision(attacker, damage, ignoredEntities, effects, onHit);
            shootAtAngle(collision1, attacker, interval * i, radius);
            attacker.level().addFreshEntity(collision1);
            collisions[j - 1 - i] = collision1;

            WeaponSwingCollision collision2 = new WeaponSwingCollision(attacker, damage, ignoredEntities, effects, onHit);
            shootAtAngle(collision2, attacker, -(interval * i), radius);
            attacker.level().addFreshEntity(collision2);
            collisions[j - 1 + i] = collision2;
        }

        WeaponSwingCollision collision = new WeaponSwingCollision(attacker, damage, ignoredEntities, effects, onHit);
        collision.shootFromRotation(attacker, attacker.getXRot(), attacker.getYRot(), 0, radius, 0);
        attacker.level().addFreshEntity(collision);
        collisions[j - 1] = collision;
        return collisions;
    }

    public static WeaponSwingCollision[] lowFlyingSectorRange(Entity attacker, float radius, float angle, int count, float damage) {
        return lowFlyingSectorRange(attacker, radius, angle, count, damage, new HashSet<>(), (p) -> {});
    }

    public static WeaponSwingCollision[] lowFlyingSectorRange(Entity attacker, float radius, float angle, int count, Set<MobEffectInstance> effects) {
        return lowFlyingSectorRange(attacker, radius, angle, count, 0, effects, (p) -> {});
    }

    public static WeaponSwingCollision[] lowFlyingSectorRange(Entity attacker, float radius, float angle, int count, float damage, Set<MobEffectInstance> effects, Consumer<Entity> onHit) {
        float yRot = attacker.getYRot();
        HashSet<Entity> ignoredEntities = new HashSet<>();

        if (count % 2 == 0) {
            count++;
        }

        WeaponSwingCollision[] collisions = new WeaponSwingCollision[count];
        int j = count / 2 + 1;
        float interval = angle / count - 1;

        for (int i = 0; i < j; i++) {
            WeaponSwingCollision collision1 = new WeaponSwingCollision(attacker, attacker.position(), damage, ignoredEntities, effects, onHit);
            collision1.shootFromRotation(attacker, 0, yRot + i * interval, 0, radius, 0);
            attacker.level().addFreshEntity(collision1);
            collisions[j - 1 - i] = collision1;

            WeaponSwingCollision collision2 = new WeaponSwingCollision(attacker, attacker.position(), damage, ignoredEntities, effects, onHit);
            collision2.shootFromRotation(attacker, 0, yRot - i * interval, 0, radius, 0);
            attacker.level().addFreshEntity(collision2);
            collisions[j - 1 + i] = collision2;
        }

        WeaponSwingCollision collision = new WeaponSwingCollision(attacker, attacker.position(), damage, ignoredEntities, effects, onHit);
        collision.shootFromRotation(attacker, 0, yRot, 0, radius, 0);
        attacker.level().addFreshEntity(collision);
        collisions[j - 1] = collision;
        return collisions;
    }

    //测试代码，因为判定线是看不到的，所以有必要来个可以看到的投掷物来预览一下
    public static void testSectorRange(Entity attacker, float radius, float angle, int count, float damage, Set<MobEffectInstance> effects) {
        HashSet<Entity> ignoredEntities = new HashSet<>();

        if (count % 2 == 0) {
            count++;
        }

        int j = count / 2 + 1;
        float interval = angle / count - 1;

        for (int i = 1; i < j; i++) {
            DioriteMissile missile1 = new DioriteMissile(attacker.level(), (LivingEntity) attacker, (byte) 0);
            shootAtAngle(missile1, attacker, interval * i, radius);
            attacker.level().addFreshEntity(missile1);

            DioriteMissile missile2 = new DioriteMissile(attacker.level(), (LivingEntity) attacker, (byte) 0);
            shootAtAngle(missile2, attacker, -(interval * i), radius);
            attacker.level().addFreshEntity(missile2);
        }

        DioriteMissile missile = new DioriteMissile(attacker.level(), (LivingEntity) attacker, (byte) 0);
        missile.shootFromRotation(attacker, attacker.getXRot(), attacker.getYRot(), 0, radius, 0);
        attacker.level().addFreshEntity(missile);
    }

    public static void testLowFlyingSectorRange(Entity attacker, float radius, float angle, int count, float damage, Set<MobEffectInstance> effects) {
        float yRot = attacker.getYRot();
        HashSet<Entity> ignoredEntities = new HashSet<>();

        if (count % 2 == 0) {
            count++;
        }

        int j = count / 2 + 1;
        float interval = angle / count - 1;

        for (int i = 0; i < j; i++) {
            DioriteMissile missile1 = new DioriteMissile(attacker.level(), (LivingEntity) attacker, (byte) 0);
            missile1.shootFromRotation(attacker, 0, yRot + i * interval, 0, radius, 0);
            missile1.setPos(missile1.getX(), attacker.getY(), missile1.getZ());
            DioriteMissile missile2 = new DioriteMissile(attacker.level(), (LivingEntity) attacker, (byte) 0);
            missile2.shootFromRotation(attacker, 0, yRot - i * interval, 0, radius, 0);
            missile2.setPos(missile2.getX(), attacker.getY(), missile2.getZ());
            attacker.level().addFreshEntity(missile1);
            attacker.level().addFreshEntity(missile2);
        }

        DioriteMissile missile = new DioriteMissile(attacker.level(), (LivingEntity) attacker, (byte) 0);
        missile.shootFromRotation(attacker, 0, yRot, 0, radius, 0);
        missile.setPos(missile.getX(), attacker.getY(), missile.getZ());
        attacker.level().addFreshEntity(missile);
    }
}
